﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;

namespace Dazaza.Framework.Entity
{
    public static class EntityBuilder
    {
        private static Dictionary<Type, Dictionary<string, PropertyDataBindingInfo>> s_TypeMappingInfo =
        new Dictionary<Type, Dictionary<string, PropertyDataBindingInfo>>();

        private static object s_SyncMappingInfo = new object();

        private static T DoBuildEntity<T>(DataRow dr) where T : class, new()
        {
            T obj = new T();
            DoFillEntity(dr, obj, typeof(T));
            return obj;
        }

        public static T BuildEntity<T>(DataRow dr) where T : class, new()
        {
            T obj = new T();
            DoFillEntity(dr, obj, typeof(T));
            return obj;
        }

        private static void DoFillEntity(DataRow dr, Object obj, Type type)
        {
            if (dr == null)
            {
                return;
            }
            DataTable dt = dr.Table;
            // fill properties
            foreach (DataColumn dataColumn in dt.Columns)
            {
                string mappingName = dataColumn.ColumnName ;
                PropertyDataBindingInfo propertyBindingInfo = GetPropertyInfo(type, mappingName);
                if (propertyBindingInfo == null)
                    continue;

                if (dr[mappingName] != DBNull.Value )
                {
                    object val = dr[mappingName];
                    Type propType = propertyBindingInfo.PropertyInfo.PropertyType;
                    if (propType == typeof(string))
                    {
                        val = val.ToString().Trim();
                    }
                    if (propType.IsEnum && Enum.IsDefined(propType, val.ToString()))
                    {
                        propertyBindingInfo.PropertyInfo.SetValue(obj, Enum.Parse(propType, val.ToString(), true), null);
                    }
                    else
                    {
                        propertyBindingInfo.PropertyInfo.SetValue(obj, val, null);
                    }
                }
            }
        }

        private static PropertyDataBindingInfo GetPropertyInfo(Type type, string columnName)
        {
            Dictionary<string, PropertyDataBindingInfo> propertyInfoList;

            s_TypeMappingInfo.TryGetValue(type, out propertyInfoList);
            if (propertyInfoList == null)
            {
                lock (s_SyncMappingInfo)
                {
                    s_TypeMappingInfo.TryGetValue(type, out propertyInfoList);
                    if (propertyInfoList == null)
                    {
                        AddTypeInfo(type);
                        propertyInfoList = s_TypeMappingInfo[type];
                    }
                }
            }
         

            PropertyDataBindingInfo info;
            propertyInfoList.TryGetValue(columnName, out info);
            return info;
        }

        private static void AddTypeInfo(Type type)
        {

            Dictionary<Type, Dictionary<string, PropertyDataBindingInfo>> newMappingList =
                new Dictionary<Type, Dictionary<string, PropertyDataBindingInfo>>(s_TypeMappingInfo);

            Dictionary<string, PropertyDataBindingInfo> mappingInfos;

            GetTypeInfo(type, out mappingInfos);

            newMappingList[type] = mappingInfos;

            s_TypeMappingInfo = newMappingList;
 
        }

        private static void GetTypeInfo(Type type, out Dictionary<string, PropertyDataBindingInfo> mappingInfos)
        {
            mappingInfos = new Dictionary<string, PropertyDataBindingInfo>();

            PropertyInfo[] propertyList = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (PropertyInfo propertyInfo in propertyList)
            {
                Object[] attributes = propertyInfo.GetCustomAttributes(false);
                foreach (Object attribute in attributes)
                {
                    if (attribute is DataMappingAttribute)
                    {
                        DataMappingAttribute obj = attribute as DataMappingAttribute;
                        PropertyDataBindingInfo propertyDataBindingInfo = new PropertyDataBindingInfo(obj, propertyInfo);
                        mappingInfos.Add(obj.ColumnName, propertyDataBindingInfo);
                        continue;
                    }
                }
            }
        }
    }

    #region PropertyDataBindingInfo
    /// <summary>
    /// Contains data mapping info for a property in a type
    /// </summary>
    public class PropertyDataBindingInfo
    {
        public DataMappingAttribute DataMapping;
        public PropertyInfo PropertyInfo;
        public PropertyDataBindingInfo(DataMappingAttribute mapping, PropertyInfo propertyInfo)
        {
            DataMapping = mapping;
            PropertyInfo = propertyInfo;
        }
    }
    #endregion
}
